#include "tdp2_liveresource.hpp"
#include "tdp2_renderdevice.hpp"

namespace TDP
{
	namespace Resources
	{
		LiveTextureResource::LiveTextureResource(Rendering::IRenderDevice *device, TextureSpec::TextureFormat tf,
				TextureSpec::TextureLayout tl, Int numMipMaps, Int cachedMipMaps, Int width, Int height, Int depth, UInt32 flags)
		{
			if(cachedMipMaps < 1 || cachedMipMaps > numMipMaps || width < 1 || height < 1 || depth < 1)
				return;
			UInt32 uwidth = static_cast<UInt32>(width);
			UInt32 uheight = static_cast<UInt32>(height);
			UInt32 udepth = static_cast<UInt32>(depth);

			if(numMipMaps >= 29)
				return;	// Too big to work with

			UInt32 minWidth = 1;
			UInt32 minHeight = 1;
			switch(tf)
			{
			case TextureSpec::TF_DXT1:
			case TextureSpec::TF_DXT3:
			case TextureSpec::TF_DXT5:
				minWidth = minHeight = 4;
				break;
			};

			// Verify
			UInt32 lowWidth = uwidth;
			UInt32 lowHeight = uheight;
			UInt32 lowDepth = udepth;
			for(Int i=0;i<numMipMaps;i++)
			{
				lowWidth /= 2;
				lowHeight /= 2;
				lowDepth /= 2;
				if(lowWidth == 0 || lowHeight == 0 || (lowDepth == 0 && depth != 1))
					return;		// Bad cache size
			}

			_lowLOD.rth = _highLOD.rth = _pendingLOD.rth = 0;

			_texFormat = tf;
			_texLayout = tl;
			_numMipMaps = numMipMaps;
			_cachedMipMaps = cachedMipMaps;
			_width = width;
			_height = height;
			_depth = depth;
			_flags = flags;
			_device = device;
			_initialized = true;
		}

		bool LiveTextureResource::DigestStreamData(const void *data, LargeUInt numBytesAvailable, LargeUInt *outNumDigested)
		{
			LargeUInt digested = 0;
			*outNumDigested = 0;
			if(!_initialized)
				return false;
			Rendering::RenderTextureHandle rth = _pendingLOD.rth;
			if(rth == 0)
				return false;	// Don't have a texture

			UInt32 clusterWidth = 1;
			UInt32 clusterHeight = 1;
			UInt32 clusterSizeBytes = 0;
			UInt32 sides = 1;

			if(_texLayout == TextureSpec::TL_Cube)
				sides = 6;

			switch(_texFormat)
			{
			case TextureSpec::TF_DXT1:
				clusterWidth = clusterHeight = 4;
				clusterSizeBytes = 8;
				break;
			case TextureSpec::TF_DXT3:
				clusterWidth = clusterHeight = 4;
				clusterSizeBytes = 16;
				break;
			case TextureSpec::TF_DXT5:
				clusterWidth = clusterHeight = 4;
				clusterSizeBytes = 16;
				break;
			case TextureSpec::TF_A8:
				clusterSizeBytes = 1;
				break;
			case TextureSpec::TF_RGBA8:
				clusterSizeBytes = 4;
				break;
			case TextureSpec::TF_RGB8:
				clusterSizeBytes = 3;
				break;
			case TextureSpec::TF_RGBA5551:
			case TextureSpec::TF_RGBA4:
			case TextureSpec::TF_RGB565:
				clusterSizeBytes = 2;
				break;
			};


			while(true)
			{
				LargeUInt roundDigested = 0;

				// Device levels are more detailed to less detailed, but streaming is low to high
				UInt32 deviceLevel = _pendingLOD.numLevels - _pendingLOD.levelsLoaded - 1;

				UInt32 hClusters = (_pendingLOD.width >> deviceLevel) / clusterWidth;
				UInt32 vClusters = (_pendingLOD.height >> deviceLevel) / clusterHeight;
				UInt32 lDepth = (_pendingLOD.depth >> deviceLevel);
				if(lDepth == 0)
					lDepth = 1;

				LargeUInt clustersAvailable = numBytesAvailable / clusterSizeBytes;
				LargeUInt clusterRowsAvailable = clustersAvailable / hClusters;
				
				if(clustersAvailable == 0)
				{
					// Not enough data to continue
					*outNumDigested = digested;
					return true;
				}

				bool multiRow = (_pendingX == 0 && clusterRowsAvailable >= 1);
				if(multiRow)
				{
					// Full row
					UInt32 vSpan = vClusters - _pendingY;
					if(vSpan > clusterRowsAvailable)
						vSpan = static_cast<UInt32>(clusterRowsAvailable);
					roundDigested = vSpan * hClusters * clusterSizeBytes;
					_device->UploadTextureRows(rth, deviceLevel, _pendingSide, _pendingY * clusterHeight, _pendingZ, vSpan * clusterHeight, data, roundDigested);
					_pendingY += vSpan;
				}
				else
				{
					// Intra-row
					UInt32 hSpan = hClusters - _pendingX;
					if(hSpan > clustersAvailable)
						hSpan = static_cast<UInt32>(clusterRowsAvailable);
					roundDigested = hSpan * clusterSizeBytes;
					_device->UploadTextureSubRow(rth, deviceLevel, _pendingSide, _pendingX * clusterWidth, _pendingY * clusterHeight, _pendingZ, hSpan * clusterWidth, data, roundDigested);
					_pendingX += hSpan;
				}
				
				// Move forward in the data
				data = reinterpret_cast<const UInt8 *>(data) + roundDigested;
				numBytesAvailable -= roundDigested;
				digested += roundDigested;

				if(_pendingX == hClusters)
				{
					_pendingX = 0;
					_pendingY++;
				}
				if(_pendingY == vClusters)
				{
					_pendingY = 0;
					_pendingZ++;
				}
				if(_pendingZ == lDepth)
				{
					_pendingZ = 0;
					_pendingSide++;
				}
				if(_pendingSide == sides)
				{
					_pendingSide = 0;
					_pendingLOD.levelsLoaded++;
					_device->SetTextureMinLOD(rth, deviceLevel);

					// If this is higher-resolution than the high LOD, throw out the high LOD
					if(_highLOD.rth != 0 && _highLOD.levelsLoaded <= _pendingLOD.levelsLoaded)
					{
						_device->DestroyTexture(_highLOD.rth);
						_highLOD.rth = 0;
					}
				}
				if(_pendingLOD.levelsLoaded == _pendingLOD.numLevels)
				{
					if(_lowLOD.rth == 0)
						_lowLOD = _pendingLOD;
					else
					{
						if(_highLOD.rth != 0)
							_device->DestroyTexture(_highLOD.rth);
						_highLOD = _pendingLOD;
					}
					_pendingLOD.rth = 0;
					
					*outNumDigested = digested;
					return false;
				}
			}
		}

		// Returns the texture owned by this resource with the highest LOD
		Rendering::RenderTextureHandle LiveTextureResource::BestLOD() const
		{
			Rendering::RenderTextureHandle rth = _lowLOD.rth;
			UInt32 levels = ((rth == 0) ? 0 : _lowLOD.levelsLoaded);

			if(_pendingLOD.rth != 0 && _pendingLOD.levelsLoaded > levels)
			{
				rth = _pendingLOD.rth;
				levels = _pendingLOD.levelsLoaded;
			}

			if(_highLOD.rth != 0 && _highLOD.levelsLoaded > levels)
			{
				rth = _highLOD.rth;
				levels = _highLOD.levelsLoaded;
			}

			return rth;
		}


		void LiveTextureResource::StartStreaming(ResourceManagement::ResourcePreloader *preloader, UInt32 levelTarget)
		{
			if(_pendingLOD.rth)
			{
				_device->DestroyTexture(_pendingLOD.rth);
				_pendingLOD.rth = 0;
			}

			UInt32 reductions = static_cast<UInt32>(this->_numMipMaps) - levelTarget;

			_pendingLOD.width = _width >> reductions;
			_pendingLOD.height = _height >> reductions;
			_pendingLOD.depth = ReduceDepth(reductions);
			_pendingLOD.numLevels = levelTarget;
			_pendingLOD.levelsLoaded = 0;

			_pendingX = _pendingY = _pendingZ = _pendingSide = 0;

			if(!_device->CreateTexture(_texFormat, _texLayout, levelTarget, _pendingLOD.width, _pendingLOD.height, _pendingLOD.depth, _flags, &_pendingLOD.rth))
				return;		// Failed
		}

		LiveTextureResource::~LiveTextureResource()
		{
			if(!_initialized)
				return;

			if(_lowLOD.rth)
				_device->DestroyTexture(_lowLOD.rth);
			if(_highLOD.rth)
				_device->DestroyTexture(_highLOD.rth);
			if(_pendingLOD.rth)
				_device->DestroyTexture(_pendingLOD.rth);
		}
	}
}
